package handlers

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"gitee.com/konglinglong/car/backend/internal/models"
	"gitee.com/konglinglong/car/backend/internal/storage"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// SupportedArtifactTypes defines the supported artifact file types
var SupportedArtifactTypes = map[string]string{
	".jar":  "application/java-archive",
	".war":  "application/java-archive",
	".tar":  "application/x-tar",
	".gz":   "application/gzip",
	".zip":  "application/zip",
	".tgz":  "application/gzip",
	".rpm":  "application/x-rpm",
	".deb":  "application/x-debian-package",
	".apk":  "application/vnd.android.package-archive",
	".ipa":  "application/octet-stream",
	".msi":  "application/x-msi",
	".dmg":  "application/x-apple-diskimage",
	".exe":  "application/x-msdownload",
	".bin":  "application/octet-stream",
	".so":   "application/x-sharedlib",
	".dll":  "application/x-msdownload",
	".lib":  "application/x-archive",
	".a":    "application/x-archive",
	".whl":  "application/zip",
	".gem":  "application/x-tar",
	".nupkg": "application/zip",
}

// ArtifactMetadata represents artifact metadata
type ArtifactMetadata struct {
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	Version     string            `json:"version"`
	Type        string            `json:"type"`
	Size        int64             `json:"size"`
	Checksum    string            `json:"checksum"`
	MimeType    string            `json:"mimeType"`
	Repository  string            `json:"repository"`
	UploadedBy  string            `json:"uploadedBy"`
	UploadedAt  time.Time         `json:"uploadedAt"`
	Description string            `json:"description,omitempty"`
	Labels      map[string]string `json:"labels,omitempty"`
	Downloads   int64             `json:"downloads"`
}

// UploadArtifactRequest represents the upload request
type UploadArtifactRequest struct {
	Repository  string            `form:"repository" binding:"required"`
	Name        string            `form:"name" binding:"required"`
	Version     string            `form:"version" binding:"required"`
	Description string            `form:"description"`
	Labels      map[string]string `form:"labels"`
}

// UploadArtifact handles artifact upload
func (h *Handlers) UploadArtifact(c *gin.Context) {
	var req UploadArtifactRequest
	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Get uploaded file
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "No file uploaded"})
		return
	}
	defer file.Close()

	// Validate file type
	ext := strings.ToLower(filepath.Ext(header.Filename))
	mimeType, supported := SupportedArtifactTypes[ext]
	if !supported {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": fmt.Sprintf("Unsupported file type: %s", ext),
			"supported_types": getSupportedTypes(),
		})
		return
	}

	// Check if repository exists
	var repo models.Repository
	if err := h.db.Where("name = ?", req.Repository).First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Generate artifact ID and storage path
	artifactID := uuid.New().String()
	storagePath := fmt.Sprintf("artifacts/%s/%s/%s/%s-%s%s", 
		req.Repository, req.Name, req.Version, req.Name, req.Version, ext)

	// Upload file to storage
	ctx := context.Background()
	opts := storage.PutObjectOptions{
		ContentType: mimeType,
	}
	if err := h.storage.PutObject(ctx, storagePath, file, header.Size, opts); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to upload file"})
		return
	}

	// Calculate checksum (simplified - in production, use proper hashing)
	checksum := fmt.Sprintf("sha256:%s", artifactID[:16])

	// Get current user (simplified)
	userID := "admin" // In real implementation, get from JWT token

	// Create artifact record
	artifact := models.Artifact{
		ID:          artifactID,
		Name:        req.Name,
		Version:     req.Version,
		Type:        ext[1:], // Remove the dot
		Size:        header.Size,
		Checksum:    checksum,
		MimeType:    mimeType,
		StoragePath: storagePath,
		RepositoryID: repo.ID,
		UploadedBy:  userID,
		Description: req.Description,
		Downloads:   0,
	}

	if err := h.db.Create(&artifact).Error; err != nil {
		// Cleanup uploaded file on database error
		h.storage.DeleteObject(ctx, storagePath)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save artifact metadata"})
		return
	}

	// Return artifact metadata
	metadata := ArtifactMetadata{
		ID:          artifact.ID,
		Name:        artifact.Name,
		Version:     artifact.Version,
		Type:        artifact.Type,
		Size:        artifact.Size,
		Checksum:    artifact.Checksum,
		MimeType:    artifact.MimeType,
		Repository:  req.Repository,
		UploadedBy:  artifact.UploadedBy,
		UploadedAt:  artifact.CreatedAt,
		Description: artifact.Description,
		Downloads:   artifact.Downloads,
	}

	c.JSON(http.StatusCreated, gin.H{
		"message": "Artifact uploaded successfully",
		"artifact": metadata,
	})
}

// DownloadArtifact handles artifact download
func (h *Handlers) DownloadArtifact(c *gin.Context) {
	repoName := c.Param("repository")
	artifactName := c.Param("name")
	version := c.Param("version")

	// Find artifact
	var artifact models.Artifact
	var repo models.Repository

	if err := h.db.Where("name = ?", repoName).First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	if err := h.db.Where("repository_id = ? AND name = ? AND version = ?", 
		repo.ID, artifactName, version).First(&artifact).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Artifact not found"})
		return
	}

	// Get file from storage
	ctx := context.Background()
	reader, err := h.storage.GetObject(ctx, artifact.StoragePath)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve file"})
		return
	}
	defer reader.Close()

	// Increment download count
	h.db.Model(&artifact).Update("downloads", artifact.Downloads+1)

	// Set response headers
	filename := fmt.Sprintf("%s-%s.%s", artifact.Name, artifact.Version, artifact.Type)
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
	c.Header("Content-Type", artifact.MimeType)
	c.Header("Content-Length", strconv.FormatInt(artifact.Size, 10))
	c.Header("X-Checksum", artifact.Checksum)

	// Stream file to client
	io.Copy(c.Writer, reader)
}

// ListArtifactsByRepository lists artifacts in a repository
func (h *Handlers) ListArtifactsByRepository(c *gin.Context) {
	repoName := c.Param("repository")
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
	artifactType := c.Query("type")

	// Find repository
	var repo models.Repository
	if err := h.db.Where("name = ?", repoName).First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Build query
	query := h.db.Where("repository_id = ?", repo.ID)
	if artifactType != "" {
		query = query.Where("type = ?", artifactType)
	}

	// Get total count
	var total int64
	query.Model(&models.Artifact{}).Count(&total)

	// Get artifacts with pagination
	var artifacts []models.Artifact
	offset := (page - 1) * limit
	query.Offset(offset).Limit(limit).Order("created_at DESC").Find(&artifacts)

	// Convert to metadata format
	var artifactList []ArtifactMetadata
	for _, artifact := range artifacts {
		artifactList = append(artifactList, ArtifactMetadata{
			ID:          artifact.ID,
			Name:        artifact.Name,
			Version:     artifact.Version,
			Type:        artifact.Type,
			Size:        artifact.Size,
			Checksum:    artifact.Checksum,
			MimeType:    artifact.MimeType,
			Repository:  repoName,
			UploadedBy:  artifact.UploadedBy,
			UploadedAt:  artifact.CreatedAt,
			Description: artifact.Description,
			Downloads:   artifact.Downloads,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"artifacts": artifactList,
		"pagination": gin.H{
			"page":  page,
			"limit": limit,
			"total": total,
			"pages": (total + int64(limit) - 1) / int64(limit),
		},
	})
}

// GetArtifactMetadata returns artifact metadata
func (h *Handlers) GetArtifactMetadata(c *gin.Context) {
	repoName := c.Param("repository")
	artifactName := c.Param("name")
	version := c.Param("version")

	// Find artifact
	var artifact models.Artifact
	var repo models.Repository

	if err := h.db.Where("name = ?", repoName).First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	if err := h.db.Where("repository_id = ? AND name = ? AND version = ?", 
		repo.ID, artifactName, version).First(&artifact).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Artifact not found"})
		return
	}

	metadata := ArtifactMetadata{
		ID:          artifact.ID,
		Name:        artifact.Name,
		Version:     artifact.Version,
		Type:        artifact.Type,
		Size:        artifact.Size,
		Checksum:    artifact.Checksum,
		MimeType:    artifact.MimeType,
		Repository:  repoName,
		UploadedBy:  artifact.UploadedBy,
		UploadedAt:  artifact.CreatedAt,
		Description: artifact.Description,
		Downloads:   artifact.Downloads,
	}

	c.JSON(http.StatusOK, metadata)
}

// DeleteArtifactByVersion deletes a specific version of an artifact
func (h *Handlers) DeleteArtifactByVersion(c *gin.Context) {
	repoName := c.Param("repository")
	artifactName := c.Param("name")
	version := c.Param("version")

	// Find artifact
	var artifact models.Artifact
	var repo models.Repository

	if err := h.db.Where("name = ?", repoName).First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	if err := h.db.Where("repository_id = ? AND name = ? AND version = ?", 
		repo.ID, artifactName, version).First(&artifact).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Artifact not found"})
		return
	}

	// Delete from storage
	ctx := context.Background()
	if err := h.storage.DeleteObject(ctx, artifact.StoragePath); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete file from storage"})
		return
	}

	// Delete from database
	if err := h.db.Delete(&artifact).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete artifact metadata"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Artifact deleted successfully"})
}

// ListArtifactVersions lists all versions of a specific artifact
func (h *Handlers) ListArtifactVersions(c *gin.Context) {
	repoName := c.Param("repository")
	artifactName := c.Param("name")

	// Find repository
	var repo models.Repository
	if err := h.db.Where("name = ?", repoName).First(&repo).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Repository not found"})
		return
	}

	// Get all versions of the artifact
	var artifacts []models.Artifact
	if err := h.db.Where("repository_id = ? AND name = ?", repo.ID, artifactName).
		Order("created_at DESC").Find(&artifacts).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve artifact versions"})
		return
	}

	if len(artifacts) == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "Artifact not found"})
		return
	}

	// Convert to metadata format
	var versions []ArtifactMetadata
	for _, artifact := range artifacts {
		versions = append(versions, ArtifactMetadata{
			ID:          artifact.ID,
			Name:        artifact.Name,
			Version:     artifact.Version,
			Type:        artifact.Type,
			Size:        artifact.Size,
			Checksum:    artifact.Checksum,
			MimeType:    artifact.MimeType,
			Repository:  repoName,
			UploadedBy:  artifact.UploadedBy,
			UploadedAt:  artifact.CreatedAt,
			Description: artifact.Description,
			Downloads:   artifact.Downloads,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"artifact": artifactName,
		"repository": repoName,
		"versions": versions,
		"total": len(versions),
	})
}

// GetSupportedTypes returns list of supported artifact types
func (h *Handlers) GetSupportedTypes(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"supported_types": getSupportedTypes(),
	})
}

// Helper function to get supported types
func getSupportedTypes() []string {
	types := make([]string, 0, len(SupportedArtifactTypes))
	for ext := range SupportedArtifactTypes {
		types = append(types, ext)
	}
	return types
}

// SearchArtifacts searches artifacts across repositories
func (h *Handlers) SearchArtifacts(c *gin.Context) {
	query := c.Query("q")
	artifactType := c.Query("type")
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))

	if query == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Search query is required"})
		return
	}

	// Build search query
	dbQuery := h.db.Where("name LIKE ? OR description LIKE ?", 
		"%"+query+"%", "%"+query+"%")
	
	if artifactType != "" {
		dbQuery = dbQuery.Where("type = ?", artifactType)
	}

	// Get total count
	var total int64
	dbQuery.Model(&models.Artifact{}).Count(&total)

	// Get artifacts with pagination
	var artifacts []models.Artifact
	offset := (page - 1) * limit
	dbQuery.Offset(offset).Limit(limit).Order("downloads DESC, created_at DESC").Find(&artifacts)

	// Get repository names for each artifact
	var results []map[string]interface{}
	for _, artifact := range artifacts {
		var repo models.Repository
		h.db.Where("id = ?", artifact.RepositoryID).First(&repo)
		
		results = append(results, map[string]interface{}{
			"id":          artifact.ID,
			"name":        artifact.Name,
			"version":     artifact.Version,
			"type":        artifact.Type,
			"size":        artifact.Size,
			"checksum":    artifact.Checksum,
			"repository":  repo.Name,
			"uploaded_by": artifact.UploadedBy,
			"uploaded_at": artifact.CreatedAt,
			"description": artifact.Description,
			"downloads":   artifact.Downloads,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"query": query,
		"results": results,
		"pagination": gin.H{
			"page":  page,
			"limit": limit,
			"total": total,
			"pages": (total + int64(limit) - 1) / int64(limit),
		},
	})
}